package JackAnalyzer;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;

public class CompilationEngine {
	private PrintWriter out;
	private JackTokenizer tokenizer;
	private String space;
	private HashMap<String,String> vars;
	private ArrayList<String> subroutines;

	private final static String IDENTIFIER_CONST = "identifier";
	private final static String KEYWORD_CONT = "keyword";


	
	public CompilationEngine(File input , String output){
		try {
			out = new PrintWriter(new BufferedWriter(new FileWriter(new File(output))));
			tokenizer = new JackTokenizer(input);
			vars = new HashMap<String,String>();
			subroutines = new ArrayList<String>();
			space = "";
		} catch (IOException e) {
			throw new RuntimeException("Failed to open\\create " + output + " target file");
		}
	}

	public void close(){
		out.close();
	}

	public void compileClass(){
		out.println("<class>");
		moreSpace();
		//class keyword
		tokenizer.checkAndAdvance();
		printTag( KEYWORD_CONT , tokenizer.keyWord());

		//class name
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}else
			fail("Illegal class identifer");
		//open { class
		tokenizer.checkAndAdvance();
		if(checkSymbol("{")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("there is no { to open the class");
		//var dec declaration
		tokenizer.checkAndAdvance();
		while(JackData.isClassVar(tokenizer.tokenType(), tokenizer.keyWord())){
			compileClassVarDec();
			tokenizer.checkAndAdvance();
		}
		//class subroutine dec 
		while(JackData.isSubRoutine(tokenizer.tokenType(), tokenizer.keyWord())){
			compileSubroutine();
			tokenizer.checkAndAdvance();
		}

		if(checkSymbol("}")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("no closing } for class");
		lessSpace();
		out.println("</class>");
	}

	public void compileClassVarDec(){
		out.println(space + "<classVarDec>");
		moreSpace();
		String type = "";
		//print field/static

		printTag( KEYWORD_CONT , tokenizer.keyWord());
		//print variable type
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( "keyword" , tokenizer.keyWord());
			type = tokenizer.keyWord();
		}
		else if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}else
			fail("Illegal class vars variable type");
		//print variable name
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
			vars.put(tokenizer.identifier() , type);
		}else
			fail("Illegal class vars variable name");

		tokenizer.checkAndAdvance();
		while(checkSymbol(",")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			if(tokenizer.tokenType() == JackData.KEYWORD){
				printTag( "keyword" , tokenizer.keyWord());
				type = tokenizer.keyWord();
			}
			else if(tokenizer.tokenType() == JackData.IDENTIFIER){
				printTag( IDENTIFIER_CONST , tokenizer.identifier());
				vars.put(tokenizer.identifier() , type);
			}else
				fail("Illegal class identifer");
			tokenizer.checkAndAdvance();
		}

		if(checkSymbol(";")){
			printTag("symbol", tokenizer.symbol());
		}else{

			fail("No ending ; for class vars");
		}

		lessSpace();
		out.println(space + "</classVarDec>");
	}

	public void compileSubroutine(){
		out.println(space + "<subroutineDec>");
		moreSpace();
		//print function or method
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag(KEYWORD_CONT, tokenizer.keyWord());
		}else
			fail("Illegal subroutine dec");
		//print return value
		tokenizer.checkAndAdvance();

		if(tokenizer.tokenType() == JackData.KEYWORD && JackData.returnValue(tokenizer.keyWord())){
			printTag(KEYWORD_CONT, tokenizer.keyWord());
		}
		else if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}
		else
			fail("Illegal subroutine return value");
		//print subroutine name
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
			subroutines.add(tokenizer.identifier());
		}else
			fail("Illegal subroutine name identifer");
		//print opening ( subroutine
		tokenizer.checkAndAdvance();
		if(checkSymbol("(")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("no opening ( for subroutine");
		//print parameter list
		tokenizer.checkAndAdvance();
		compileParameterList();
		//print closing ) subroutine
		if(checkSymbol(")")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("no closing ) for subroutine");
		//compile subroutine body
		tokenizer.checkAndAdvance();
		compileSubroutineBody();

		lessSpace();
		out.println(space + "</subroutineDec>");
	}

	public void compileParameterList(){
		out.println(space + "<parameterList>");
		String key = "";
		if(checkSymbol(")")){
			out.println(space + "</parameterList>");
			return;
		}
		moreSpace();
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag("keyword", tokenizer.keyWord());
			key = tokenizer.keyWord();
			tokenizer.checkAndAdvance();
		}else if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag(IDENTIFIER_CONST, tokenizer.identifier());
			tokenizer.checkAndAdvance();
		}
		else
			fail("Illegal subroutine parameter");

		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag(IDENTIFIER_CONST, tokenizer.identifier());
			vars.put(tokenizer.identifier(), key);
			tokenizer.checkAndAdvance();
		}
		else
			fail("Illegal subroutine parameter");

		while(checkSymbol(",")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			if(tokenizer.tokenType() == JackData.KEYWORD){
				printTag("keyword", tokenizer.keyWord());
				key = tokenizer.keyWord();
				tokenizer.checkAndAdvance();
			}else if(tokenizer.tokenType() == JackData.IDENTIFIER){
				printTag(IDENTIFIER_CONST, tokenizer.identifier());
				tokenizer.checkAndAdvance();
			}else
				fail("Illegal subroutine parameter");

			if(tokenizer.tokenType() == JackData.IDENTIFIER){
				printTag(IDENTIFIER_CONST, tokenizer.identifier());
				vars.put(tokenizer.identifier(), key);
				tokenizer.checkAndAdvance();
			}
			else
				fail("Illegal subroutine parameter");
		}
		lessSpace();
		out.println(space + "</parameterList>");
	}

	public void compileSubroutineBody(){
		out.println(space + "<subroutineBody>");
		moreSpace();
		//print {

		if(checkSymbol("{")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("no opening { for subroutine");
		//check for varDec
		tokenizer.checkAndAdvance();

		while(JackData.isVar(tokenizer.tokenType(), tokenizer.keyWord())){
			compileVarDec();
			tokenizer.checkAndAdvance();
		}

		//compile statements
		compileStatements();

		if(checkSymbol("}")){
			printTag("symbol", tokenizer.symbol());
		}else{
			fail("no closing } for subroutine");
		}


		lessSpace();
		out.println(space + "</subroutineBody>");
	}

	public void compileVarDec(){
		out.println(space + "<varDec>");
		moreSpace();
		String type = "";
		//print 'var'
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal 'var' dec in subroutine");
		//print var type
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
			type = tokenizer.keyWord();
		}
		else if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}else
			fail("Illegal var identifer");
		//print var name
		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
			vars.put(tokenizer.identifier() , type);
		}else
			fail("Illegal var name insubroutine");
		//print (,var)*
		tokenizer.checkAndAdvance();
		while(checkSymbol(",")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			if(tokenizer.tokenType() == JackData.KEYWORD){
				printTag( KEYWORD_CONT , tokenizer.keyWord());
				type = tokenizer.keyWord();

			}
			else if(tokenizer.tokenType() == JackData.IDENTIFIER){
				printTag( IDENTIFIER_CONST , tokenizer.identifier());
				vars.put(tokenizer.identifier() , type);
			}else
				fail("Illegal var name insubroutine");
			tokenizer.checkAndAdvance();
		}

		if(checkSymbol(";")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("Missing varDec ; ending");

		lessSpace();
		out.println(space + "</varDec>");
	}
	//In the end advance to next token
	public void compileStatements(){
		out.println(space + "<statements>");
		moreSpace();

		while(JackData.isStatement(tokenizer.tokenType(), tokenizer.keyWord())){
			if(tokenizer.keyWord().contentEquals("do")) compileDo();
			if(tokenizer.keyWord().contentEquals("let")) compileLet();
			if(tokenizer.keyWord().contentEquals("while")) compileWhile();
			if(tokenizer.keyWord().contentEquals("return")) compileReturn();
			if(tokenizer.keyWord().contentEquals("if"))compileIf();
		}
		lessSpace();
		out.println(space + "</statements>");
	}
	//In the end advance to next token
	public void compileDo(){
		out.println(space + "<doStatement>");
		moreSpace();

		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal do statement");

		tokenizer.checkAndAdvance();

		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}else
			fail("Illegal subroutineName\\className identifer in do");

		tokenizer.checkAndAdvance();
		if(checkSymbol("(")){
			printTag( "symbol" , tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileExpressionList();
			if(checkSymbol(")")){
				printTag( "symbol" , tokenizer.symbol());
			}else
				fail("missing closing ) in doStatement");

		}else if(checkSymbol(".")){
			printTag( "symbol" , tokenizer.symbol());

			tokenizer.checkAndAdvance();
			if(tokenizer.tokenType() == JackData.IDENTIFIER){
				printTag( IDENTIFIER_CONST , tokenizer.identifier());
			}else
				fail("Illegal subroutineName identifer in do");

			tokenizer.checkAndAdvance();

			if(checkSymbol("(")){
				printTag( "symbol" , tokenizer.symbol());
				tokenizer.checkAndAdvance();
				compileExpressionList();
				if(checkSymbol(")")){
					printTag( "symbol" , tokenizer.symbol());
				}else{
					fail("missing closing ) in doStatement");
				}
			}else
				fail("missing opening ( in doStatement");
		}else
			fail("missing '.' in doStatement");

		tokenizer.checkAndAdvance();

		if(checkSymbol(";")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("Missing varDec ; ending");

		tokenizer.checkAndAdvance();

		lessSpace();
		out.println(space + "</doStatement>");
	}
	//In the end advance to next token
	public void compileLet(){
		out.println(space + "<letStatement>");
		moreSpace();
		//print let statement
		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal let statement");

		//print var name
		tokenizer.checkAndAdvance();

		if(tokenizer.tokenType() == JackData.IDENTIFIER){
			printTag( IDENTIFIER_CONST , tokenizer.identifier());
		}else
			fail("Illegal varName identifer in do");
		//check if array []
		tokenizer.checkAndAdvance();

		if(checkSymbol("[")){
			//print [
			printTag("symbol", tokenizer.symbol());
			//compile expression
			tokenizer.checkAndAdvance();
			compileExpression();
			//print ]
			if(checkSymbol("]")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("No closing ] in let");
			tokenizer.checkAndAdvance();
		}
		//print =
		if(checkSymbol("=")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("No operator = in let");

		//compile expression
		tokenizer.checkAndAdvance();

		compileExpression();
		//print ;

		if(checkSymbol(";")){
			printTag("symbol", tokenizer.symbol());
		}else{
			fail("No ending ; in let");
		}
		tokenizer.checkAndAdvance();

		lessSpace();
		out.println(space + "</letStatement>");
	}
	//In the end advance to next token
	public void compileWhile(){
		out.println(space + "<whileStatement>");
		moreSpace();

		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal while statement");


		tokenizer.checkAndAdvance();

		if(checkSymbol("(")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileExpression();

			if(checkSymbol(")")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("No closing ) in while");
		}else
			fail("No opening ( in while");


		tokenizer.checkAndAdvance();
		if(checkSymbol("{")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("No opening { in let");

		tokenizer.checkAndAdvance();
		compileStatements();

		if(checkSymbol("}")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("No closing } in while");
		tokenizer.checkAndAdvance();

		lessSpace();
		out.println(space + "</whileStatement>");
	}
	//In the end advance to next token
	public void compileReturn(){
		out.println(space + "<returnStatement>");
		moreSpace();

		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal return statement");

		tokenizer.checkAndAdvance();
	
		if(!checkSymbol(";")){
			compileExpression();
		}

		if(checkSymbol(";")){
			printTag("symbol" , tokenizer.symbol());
		}else
			fail("Missing return ; ending");	
		tokenizer.checkAndAdvance();

		lessSpace();
		out.println(space + "</returnStatement>");
	}
	//In the end advance to next token
	public void compileIf(){
		out.println(space + "<ifStatement>");
		moreSpace();

		if(tokenizer.tokenType() == JackData.KEYWORD){
			printTag( KEYWORD_CONT , tokenizer.keyWord());
		}else
			fail("Illegal if statement");


		tokenizer.checkAndAdvance();

		if(checkSymbol("(")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileExpression();

			if(checkSymbol(")")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("No closing ) in if");
		}else
			fail("No opening ( in if");


		tokenizer.checkAndAdvance();
		if(checkSymbol("{")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("No opening { in if");

		tokenizer.checkAndAdvance();
		compileStatements();

		if(checkSymbol("}")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("No closing } in if");

		tokenizer.checkAndAdvance();
		if(tokenizer.tokenType() == JackData.KEYWORD && tokenizer.keyWord().contentEquals("else")){
			printTag( KEYWORD_CONT , tokenizer.keyWord());

			tokenizer.checkAndAdvance();
			if(checkSymbol("{")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("No opening { in if-else");

			tokenizer.checkAndAdvance();
			compileStatements();

			if(checkSymbol("}")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("No closing } in if-else");
			
			tokenizer.checkAndAdvance();
		}
		

		lessSpace();
		out.println(space + "</ifStatement>");
	}
	//In the end advance to next token
	public void compileExpression(){
		out.println(space + "<expression>");
		moreSpace();

		if(!compileTerm()){	
			tokenizer.checkAndAdvance();
		}
		
		while(checkSymbolOp()){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileTerm();
			tokenizer.checkAndAdvance();
		}
		lessSpace();
		out.println(space + "</expression>");
	}
	//In the end advance to next token
	public void compileExpressionList(){
		out.println(space + "<expressionList>");
		if(checkSymbol(")")){
			out.println(space + "</expressionList>");
			return;
		}
		moreSpace();

		compileExpression();

		while(checkSymbol(",")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileExpression();
		}	

		lessSpace();
		out.println(space + "</expressionList>");
	}

	public boolean compileTerm(){
		out.println(space + "<term>");
		moreSpace();
		boolean intval , stringval , keywordcons , varName ,subCall ,expression , unaryop;
		boolean result = false;
		boolean inVarName=false;

		intval = tokenizer.tokenType() == JackData.INT_CONST;
		stringval = tokenizer.tokenType() == JackData.STRING_CONST;
		keywordcons = JackData.isKeywordConstant(tokenizer.tokenType() , tokenizer.keyWord());
		varName = (tokenizer.tokenType() == JackData.IDENTIFIER) && (vars.containsKey(tokenizer.identifier()));
		if(JackAnalyzer.DEBUG){
			if(tokenizer.token().equals("shapeArray"))
				System.err.println(varName + " , " + (tokenizer.tokenType() == JackData.IDENTIFIER) + " , " + (vars.containsKey(tokenizer.identifier())));
		}
		subCall = (tokenizer.tokenType() == JackData.IDENTIFIER);
		expression = (tokenizer.tokenType() == JackData.SYMBOL) && checkSymbol("(");
		unaryop = checkSymbol("~") || checkSymbol("-");

		if(intval){
			printTag("integerConstant", tokenizer.intVal() + "");
		}

		else if(stringval){
			printTag("stringConstant", tokenizer.stringVal() + "");
		}

		else if(keywordcons){
			printTag(KEYWORD_CONT, tokenizer.keyWord());
		}
		///////////////////// VAR NAME ///////////////
		else if(varName){
			if(JackAnalyzer.DEBUG)
				System.out.println();
			inVarName = true;
			printTag(IDENTIFIER_CONST, tokenizer.identifier());
			boolean array = vars.get(tokenizer.identifier()).equals("Array");
			boolean obj = !isPrimitiveVar(tokenizer.identifier());
			if(array || obj){
				tokenizer.checkAndAdvance();
				if(checkSymbol(".")){
					printTag("symbol", tokenizer.symbol());
					tokenizer.checkAndAdvance();
					printTag(IDENTIFIER_CONST, tokenizer.identifier());
					tokenizer.checkAndAdvance();
					subroutineCall();
				}
				//var array
				else if(checkSymbol("[")){
					printTag("symbol", tokenizer.symbol());
					tokenizer.checkAndAdvance();
					compileExpression();
					if(checkSymbol("]")){
						printTag("symbol", tokenizer.symbol());
					}else
						fail("no closing ] for term");
				}else
					result = true;
			}
		}
		/////////////// UNARY OP /////////////////
		else if(unaryop){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileTerm();
		}
		////////////// EXPRESSION ////////////////
		else if(expression){ 
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();
			compileExpression();
			if(checkSymbol(")")){
				printTag("symbol", tokenizer.symbol());
			}else
				fail("no closing ) for term");
		}

		else if(subCall && !inVarName){
//			System.out.println("*****SUB CLASS******");
//			boolean subName = subroutines.contains(tokenizer.identifier());
//			boolean flag = false;
//			System.err.println("*** "+ tokenizer.token());
//			if(!subName){
//				printTag(IDENTIFIER_CONST, tokenizer.identifier());
//				tokenizer.checkAndAdvance();
//				if(checkSymbol(".")){
//					printTag("symbol", tokenizer.symbol());
//				}else if(checkSymbol("(")){
//					subroutineCall();
//					flag = true;
//				}else{
//					fail("No '.' in static function call");
//				}
//			}
//			if(!flag){
//			tokenizer.checkAndAdvance();
//			printTag(IDENTIFIER_CONST, tokenizer.identifier());
//			tokenizer.checkAndAdvance();
//			subroutineCall();
//			}

			printTag(IDENTIFIER_CONST, tokenizer.identifier());
			tokenizer.checkAndAdvance();

			if(checkSymbol(".")){
				printTag("symbol", tokenizer.symbol());
				tokenizer.checkAndAdvance();
				printTag(IDENTIFIER_CONST, tokenizer.identifier());
				tokenizer.checkAndAdvance();
			}else if(checkSymbol("(")){
			}else{
				fail("No '.' or '(' in function\\method call");
			}
			subroutineCall();
		}

		else{
			fail("Illegal term");
		}

		lessSpace();
		out.println(space + "</term>");
		return result;
	}


	private void moreSpace(){
		space+="  ";
	}

	private void lessSpace(){
		space = space.substring(2);
	}

	private void printTag(String tag , String val){
		if(tokenizer.tokenType() == JackData.SYMBOL)
			val = JackData.xmlSymbol(val);
		out.println(space + "<" + tag + "> " + val + " </" + tag + ">");
		if(JackAnalyzer.DEBUG)
			System.out.println("tag: " + val + " line: " + tokenizer.lineNum());
	}

	private void fail(String msg){
		if(JackAnalyzer.DEBUG)
			System.out.println("Line number: " + tokenizer.lineNum() + " Code: " + tokenizer.tokenType() + " Token: " + tokenizer.token());
		throw new RuntimeException(msg + " line: " + tokenizer.lineNum());
	}

	private void subroutineCall(){

		if(checkSymbol("(")){
			printTag("symbol", tokenizer.symbol());
			tokenizer.checkAndAdvance();

			compileExpressionList();
		}else
			fail("no opening ( for subroutine call");

		if(checkSymbol(")")){
			printTag("symbol", tokenizer.symbol());
		}else
			fail("no closing ) for subroutine call");
	}

	private boolean isPrimitiveVar(String name) {
		String type = vars.get(name);
		if(type != null){
			return type.contentEquals("int") ||
					type.contentEquals("char") ||
					type.contentEquals("boolean");
		}
		return false;
	}

	private boolean checkSymbol(String s){
		if(tokenizer.tokenType() == JackData.SYMBOL){
			return tokenizer.symbol().contentEquals(s);
		}
		return false;			
	}

	private boolean checkSymbolOp(){
		if(tokenizer.tokenType() == JackData.SYMBOL){

			switch (tokenizer.symbol().charAt(0)) {
			case '+':
			case '-':
			case '*':
			case '/':
			case '&':
			case '|':
			case '<':
			case '>':
			case '=':
				return true;
			}
		}
		return false;
	}

}
